package ru.fadeev.crammer.ui.window;


import java.awt.*;
import java.awt.event.*;
import java.util.Vector;
import java.util.Random;
import java.util.Locale;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableModel;

import ru.fadeev.crammer.GFIndicesStorage;
import ru.fadeev.crammer.Settings;
import ru.fadeev.crammer.TestManager;
import ru.fadeev.crammer.Utils;
import ru.fadeev.crammer.data.DictionariesManager;
import ru.fadeev.crammer.data.Dictionary;
import ru.fadeev.crammer.data.Sheet;
import ru.fadeev.crammer.data.WordRecord;
import ru.fadeev.crammer.data.ThroughWordIndex;
import ru.fadeev.crammer.ui.ArrowOptionPane;
import ru.fadeev.crammer.ui.CrammerToolbarButton;
import ru.fadeev.crammer.ui.LimitedPlainDocument;
import ru.fadeev.crammer.ui.TableTextFieldFactory;


/**
 * TestTranslationDialog class provides GUI for two types of testing: 
 * Translation - when word translations are tested, and Reverse Translation - 
 * when word reverse translations are tested.
 * @author Konstantin Fadeyev
 */
public class TestTranslationDialog extends JDialog {
	private static Color RIGHT_COLOR = new Color(210,255,210);	
	private static Color SELECTION_COLOR = new Color(220,220,255);
	private static Color SELECTION_RIGHT_COLOR = new Color(200,245,200);
	private static Color SELECTION_WRONG_COLOR = new Color(245,200,200);
    	
    private static Color WRONG_COLOR = new Color(255,210,210);;
    // Constants
	private KeyStroke KS_CHECK_ALL_WORDS = 
		KeyStroke.getKeyStroke(KeyEvent.VK_F5, InputEvent.CTRL_MASK);;
    private KeyStroke KS_CHECK_WORD = 
		KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0);
    private KeyStroke KS_SHOW_ALL_PROMPTS = 
		KeyStroke.getKeyStroke(KeyEvent.VK_F1, InputEvent.CTRL_MASK);
    private KeyStroke KS_SHOW_PROMPT = 
		KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0);

    
    // UI Components
    private JPanel bottomPanel;
	private JPanel buttonsPanel;
    private JButton checkAllWordsButton;	
    private JButton checkWordButton;
    private JButton editWordButton;
	private JPanel mainPanel;
    private JButton nextButton;
	
	private JLabel remainLabel;
	private JPanel remainPanel;
    private JButton showAllPromptsButton;
	
    private JButton showPromptButton;
	private JButton terminateButton;
	private JToolBar toolBar;
	private JMenuItem wordsPopupCheckAllWordsItem;
	private JMenuItem wordsPopupCheckWordItem;
	private JMenuItem wordsPopupEditWordItem;
	
    private JPopupMenu wordsPopupMenu;
    
    private JMenuItem wordsPopupShowAllPromptsItem;
    private JMenuItem wordsPopupShowPromptItem;
    
    private JScrollPane wordsScrollPane;   
  	private JTable wordsTable;
  	private TestTranslationTableModel wordsTableModel;

  	
  	// User fields
  	
  	/**
	 * Vector containing currently shown words and related information
	 */
    private Vector<TestTranslationRecord> currentWords;
	private DictionariesManager dictionariesManager;
	
	/**
	 * All words for test gathered in one array
	 */
    private Vector<WordRecord> heap;
    
    /**
     * Vector of word indices to keep track of word's place in dictionary
     */
    private Vector<ThroughWordIndex> heapIndices;    
    private Random random;    
  	private GFIndicesStorage storage;
  	
  	/**
  	 * One of TestManager.TRANSLATION, TestManager.REVERSE_TRANSLATION
  	 */
    private int testType;

  	
    /**
     * Constructs TestTranslationDialog and prepares data for test
     * @param owner parent window
     * @param dictionariesManager object of DictionariesManager
     * @param testType one of TestManager.TRANSLATION, 
     * TestManager.REVERSE_TRANSLATION
     * @param storage object of GFIndicesStorage
     */
    public TestTranslationDialog(JFrame owner, 
    		DictionariesManager dictionariesManager, 
    		int testType, GFIndicesStorage storage) {
    	super(owner,true);
      
        this.testType = testType;
		this.storage = storage;
		this.dictionariesManager = dictionariesManager;

		random = new Random(System.currentTimeMillis());
		heap = new Vector<WordRecord>();
		heapIndices = new Vector<ThroughWordIndex>();
		currentWords = new Vector<TestTranslationRecord>();
        
        initComponents();
        placeComponents();
               
         // Setting up window size. Initial window height is set depending 
         // on shown table rows number. 
        int rowsNumber = (testType==TestManager.TRANSLATION)
        			? Settings.getInt("translationNumberOfLines")
        			: Settings.getInt("reverseNumberOfLines");
        int windowHeight = 119 + rowsNumber * wordsTable.getRowHeight();
        
    	int x = Settings.getInt("testTranslationDialogX");
    	int y = Settings.getInt("testTranslationDialogY");
    	int w = Settings.getInt("testTranslationDialogW");
        if(x < 0 || y < 0 || w < 0) {
        	Dimension screenSize = 
        		Toolkit.getDefaultToolkit().getScreenSize();
        	setSize(screenSize.width * 9/10, windowHeight);
            setLocation(screenSize.width/20, 
            		(screenSize.height - windowHeight)/3);
        } else {
        	setSize(w, windowHeight);
        	setLocation(x, y);        	
        }
        
        if(testType == TestManager.TRANSLATION) {
        	getInputContext().selectInputMethod(new Locale("ru","RU"));
            setTitle("Translation");
        } else {
        	getInputContext().selectInputMethod(Locale.US);
            setTitle("Reverse Translation");
        }
        
        fillCurrentHeap();
        nextAction();
        refreshWordsButtons();
    }

    
    /**
     * Overwritten, so it saves window location when dialog is closed
     */
    public void setVisible(boolean v) {
    	if(v == false) {
	    	Settings.setInt("testTranslationDialogX", getX());
	    	Settings.setInt("testTranslationDialogY", getY());
	    	Settings.setInt("testTranslationDialogW", getWidth());
    	}
    	super.setVisible(v);
    }
  
    
    /**
     * Shows prompts for all currently shown words. 
     */
    public void showAllPrompts() {
    	int rowIndex = wordsTable.getSelectedRow();
    	int columnIndex = wordsTable.getSelectedColumn();    	
        for(int i=0; i<currentWords.size(); i++) {
            showPrompt(i);
        }
        if(rowIndex >=0) {
        	wordsTable.setRowSelectionInterval(rowIndex, rowIndex);
        }
        if(columnIndex >=0) {
        	wordsTable.setColumnSelectionInterval(columnIndex, columnIndex);
        }
    }
    
    
    /**
     * Shows prompt for currently selected word. 
     */
    public void showCurrentPrompt() {
    	int rowIndex = wordsTable.getSelectedRow();
    	int columnIndex = wordsTable.getSelectedColumn();   
    	showPrompt(rowIndex);
    	wordsTable.setRowSelectionInterval(rowIndex, rowIndex);
    	if(columnIndex >= 0)
    	{
    		wordsTable.setColumnSelectionInterval(columnIndex, columnIndex);
    	}
    }
    
    
    /**
     * Shows prompt for word specified by index.
     * @param index index of word in the currently shown words list
     */
    public void showPrompt(int index) {
        TestTranslationRecord record = currentWords.elementAt(index);
        record.showPrompt = true;
        wordsTableModel.fireTableDataChanged();
    }

    
    /**
     * Checks test results for all current words
     */
    private void checkAllResults() {
    	int rowIndex = wordsTable.getSelectedRow();
    	int columnIndex = wordsTable.getSelectedColumn();  
        for(int i=0; i<currentWords.size(); i++) {
            checkTestResult(i);
        }
        if(rowIndex >=0) {
        	wordsTable.setRowSelectionInterval(rowIndex, rowIndex);
        }
        if(columnIndex >=0) {
        	wordsTable.setColumnSelectionInterval(columnIndex, columnIndex);
        }
        showAllPrompts();
    }
    
    
    /**
     * Checks test result for currently selected word 
     */
    private void checkCurrentTestResult() {
    	int rowIndex = wordsTable.getSelectedRow();
    	int columnIndex = wordsTable.getSelectedColumn();  
    	checkTestResult(rowIndex);
    	wordsTable.setRowSelectionInterval(rowIndex, rowIndex);
        if(columnIndex >=0) {
        	wordsTable.setColumnSelectionInterval(columnIndex, columnIndex);
        }
        showCurrentPrompt();
    }
    
    
    
    /**
     * Checks test result for word specified by i
     * @param i index of word to check test result
     */
    private void checkTestResult(int i) {
    	Utils.finishTableEditing(wordsTable);
        if(i >= currentWords.size() || i < 0)
            return;
        TestTranslationRecord testRecord = currentWords.elementAt(i);        
        
        String userAnswer = testRecord.userAnswer;
        ThroughWordIndex wordIndex = testRecord.wordIndex;
        
		Dictionary dictionary = 
			dictionariesManager.getDictionary(wordIndex.getDictIndex());
		Sheet sheet = dictionary.getSheet(wordIndex.getSheetIndex());
        WordRecord wordRecord = sheet.getWordRecord(wordIndex.getWordIndex());
        int checkResult = TestTranslationRecord.UNKNOWN;
        String wordVersion = (testType == TestManager.TRANSLATION)
        		? wordRecord.getTranslation() 
        		: wordRecord.getWord();
        if(compareExpressions(userAnswer, wordVersion)) {
           	checkResult = TestTranslationRecord.RIGHT;
        } else {
           	checkResult = TestTranslationRecord.WRONG;
        }
        
        if(testRecord.checkResult == TestTranslationRecord.UNKNOWN) {
        	testRecord.checkResult = checkResult;
        	boolean testSuccessful = (checkResult == TestTranslationRecord.RIGHT);
        	wordRecord.addHistoryRecord(testType, testSuccessful);
        	wordRecord.refreshNeededToTest(testType, wordIndex.getDictIndex(), 
        			wordIndex.getSheetIndex());
        }
        
        testRecord.showAnswer = true;
        wordsTableModel.fireTableDataChanged();
    }
    
    
    /**
     * Compares two expressions: user answer and right answer. Each
     * expression can include several subexpressions divided by commas. 
     * Expressions are considered equal if any subexpression of the list 
     * in one expression is equals any subexpression in the list in another 
     * expression
     * @param user user answer
     * @param standard right answer
     * @return true, if user answer equals right answer
     */
    private boolean compareExpressions(String user, String standard) {
        String[] userWords = user.split(",");
        String[] standardWords = standard.split(",");
        for(int i=0; i<userWords.length; i++) {
            for(int j=0; j<standardWords.length; j++) {
                if(compareSubexpressions(userWords[i].trim(), 
                		standardWords[j].trim(), 1)) {
                    return true;
                }
            }
        }
        return false;
    }

    
    /**
     * Compares two expressions. Each expression can consist
     * of several words divided by spaces, standard expression also 
     * can contain some words closed in brackets. 
     * Part of the stadrard expression closed in brackets may be included 
     * in expression or excluded from it - function tests both cases
     * (this rule works for any number of 'bracket parts'). 
     * Expressions considered equals if every word 
     * of one exression equals every word in another expression.
     * @param user user answer
     * @param standard right answer
     * @param order function will try include and exclude all blocks 
     * that goes starting from brackety block number 'order'. 
     * Brackety blocks before block number 'order' will be included 
     * in standard expression. For order = 1 function will try include and
     * exlude all brackety blocks.   
     * @return true, if two expressions are equal
     */
    private boolean compareSubexpressions(String user, String standard, 
    		int order) {
        String[] userWords = user.split(" ");
        String[] standardWords = standard.split(" ");
        boolean equal = false;
        for(int i=0,j=0;;) {
	        if(i == userWords.length) {
	            equal = (j == standardWords.length);
	            break;
	        }
	        if(j == standardWords.length) {
	            equal = (i == userWords.length);
	            break;
	        }
	        if(userWords[i].equals(standardWords[j])) {
	            i++;
	            j++;
	        } else if(userWords[i].length() == 0) {
	            i++;
	        } else if(standardWords[j].length() == 0) {
	            j++;
	        } else {
	            equal = false;
	            break;
	        }
	    }
        if(equal) 
        	return true;
        int fi = -1;
        int li = -1;
        for(int i=0; i<order; i++) {
            fi = standard.indexOf("(",fi+1);
            li = standard.indexOf(")",li+1);
        }
        if(fi!=-1 && li!=-1) {
            String standardTwo = standard.substring(0,fi) + standard.substring(li+1);
            String standardThree = standard.substring(0,fi) +
                    standard.substring(fi+1,li)+standard.substring(li+1);
            return compareSubexpressions(user, standard, order+1) || compareSubexpressions(user,standardTwo,order) ||
                    compareSubexpressions(user, standardThree, order);
        }
        return false;
    }
	
	
	/**
     * Launches edit word dialog for currently selected word
     */
    private void editWord() {
		int index = wordsTable.getSelectedRow();
		int columnIndex = wordsTable.getSelectedColumn();
		if(index < 0 || index >= currentWords.size()) {
			return;
		}
		
		TestTranslationRecord currentRecord = currentWords.elementAt(index);
		
		WordRecord wordCopy = new WordRecord();
		wordCopy.copy(currentRecord.wordRecord);
    	
    	EditWordDialog dialog = 
    		new EditWordDialog(this, wordCopy);
    	dialog.setVisible(true);
    	
    	if(dialog.getPressedButton() == EditWordDialog.BTN_OK) {
    		int dictionaryIndex = currentRecord.wordIndex.getDictIndex();
    		int sheetIndex = currentRecord.wordIndex.getSheetIndex();
    		Dictionary dictionary = dictionariesManager.getDictionary(
    				dictionaryIndex);
    		Sheet sheet = dictionary.getSheet(sheetIndex);
    		sheet.updateWordRecord(wordCopy, currentRecord.wordIndex.getWordIndex(), 
    				storage);
    		currentRecord.wordRecord.copy(wordCopy);
    		
    		wordsTableModel.fireTableDataChanged();
    		wordsTable.setRowSelectionInterval(index, index);
            if(columnIndex >=0) {
            	wordsTable.setColumnSelectionInterval(columnIndex, columnIndex);
            }
    	}    
	}
    
    
    /**
     * Gathers all words for test in one array called heap. Then words
     * are picked up from heap randomly and presented for test.
     */
    private void fillCurrentHeap() {
        heap.removeAllElements();
        heapIndices.removeAllElements();
        dictionariesManager.startTestWordsIteration();
        while(dictionariesManager.hasNextTestWord(testType)) {
			WordRecord word = dictionariesManager.getNextTestWord();
		    heap.addElement(word);
            heapIndices.addElement(dictionariesManager.getCurrentIndex());
        }
    }

  	
  	/**
     * Checks if heap has next word
     * @return true, if heap is not empty
     */
	private boolean hasNextWord() {
		if(heap.size()==0) {
			random.setSeed(System.currentTimeMillis());
		}
		return heap.size()!=0;
	}
     
    
    /**
     * Initializes UI Components of dialog
     */
    private void initComponents() {
    	//Main panel
    	mainPanel = new JPanel();
    	
    	//Word Buttons
    	ImageIcon editWordIcon = new ImageIcon("icons/16/editWord.png");
        editWordButton = new CrammerToolbarButton(editWordIcon, "Edit Word");
        editWordButton.addActionListener(
			    new AbstractAction() {
					public void actionPerformed(ActionEvent ev) {
			        	editWord();
			        }
			    });
    	        
        ImageIcon showPromptIcon = new ImageIcon("icons/16/showPrompt.png");
        showPromptButton = new CrammerToolbarButton(showPromptIcon, 
        		"Show Prompt (" 
        		+ Utils.keyStrokeToString(KS_SHOW_PROMPT) + ")");
        AbstractAction showPromptAction = 
        	new AbstractAction() {
		        public void actionPerformed(ActionEvent ev) {
		        	showCurrentPrompt();
		        }
		    };
        showPromptButton.addActionListener(showPromptAction);
        showPromptButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
        		KS_SHOW_PROMPT, "showPrompt");
        showPromptButton.getActionMap().put("showPrompt", showPromptAction);
        
        ImageIcon showAllPromptsIcon = new ImageIcon("icons/16/showAllPrompts.png");
        showAllPromptsButton = new CrammerToolbarButton(showAllPromptsIcon, 
        		"Show All Prompts (" 
        		+ Utils.keyStrokeToString(KS_SHOW_ALL_PROMPTS) + ")");       
        AbstractAction showAllPromptsAction = 
        	new AbstractAction() {
		        public void actionPerformed(ActionEvent ev) {
		        	showAllPrompts();
		        }
		    };
        showAllPromptsButton.addActionListener(showAllPromptsAction);
        showAllPromptsButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
        		.put(KS_SHOW_ALL_PROMPTS, "showAllPrompts");
        showAllPromptsButton.getActionMap().put("showAllPrompts", 
        		showAllPromptsAction);        
        
        ImageIcon checkWordIcon = new ImageIcon("icons/16/checkWord.png");
        checkWordButton = new CrammerToolbarButton(checkWordIcon, 
        		"Check Word (" 
        		+ Utils.keyStrokeToString(KS_CHECK_WORD) + ")");
        AbstractAction checkWordAction = 
        	new AbstractAction() {
		        public void actionPerformed(ActionEvent ev) {
		        	checkCurrentTestResult();
		        }
		    };
	    checkWordButton.addActionListener(checkWordAction);
	    checkWordButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
	    		KS_CHECK_WORD, "checkWord");
	    checkWordButton.getActionMap().put("checkWord", checkWordAction);
        
        ImageIcon checkAllWordsIcon = 
        	new ImageIcon("icons/16/checkAllWords.png");
        checkAllWordsButton = new CrammerToolbarButton(checkAllWordsIcon, 
        		"Check All Words (" 
        		+ Utils.keyStrokeToString(KS_CHECK_ALL_WORDS) + ")");
        AbstractAction checkAllWordsAction = 
        	new AbstractAction() {
		        public void actionPerformed(ActionEvent ev) {
		        	checkAllResults();
		        }
		    };
	    checkAllWordsButton.addActionListener(checkAllWordsAction);
	    checkAllWordsButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
	    		.put(KS_CHECK_ALL_WORDS, "checkAllWords");
	    checkAllWordsButton.getActionMap().put("checkAllWords", 
	    		checkAllWordsAction);

      	
		toolBar = new JToolBar(JToolBar.HORIZONTAL);
		toolBar.setFloatable(false);
        
		toolBar.add(editWordButton);
		toolBar.addSeparator();
		toolBar.add(showPromptButton);
		toolBar.add(showAllPromptsButton);
		toolBar.addSeparator();
		toolBar.add(checkWordButton);
		toolBar.add(checkAllWordsButton);
        
        //Words table
        initWordsTable();
    	
    	//Popup Menu
    	initWordsPopupMenu();
    	
    	//Label 
        bottomPanel = new JPanel();       
        remainPanel = new JPanel();
        remainLabel = new  JLabel();
        
        //Next and Terminate Buttons
        buttonsPanel = new JPanel();  
      	
      	terminateButton = new JButton("Terminate");
      	terminateButton.addActionListener(
			    new AbstractAction() {
			        public void actionPerformed(ActionEvent ev) {
			        	terminateAction();
			        }
			    });
      	
      	nextButton = new JButton("Next");
      	nextButton.addActionListener(
			    new AbstractAction() {
			        public void actionPerformed(ActionEvent ev) {
			        	nextAction();
			        }
			    });
      	        
        setContentPane(mainPanel);
    }
    
    
    /**
     * Initializes popup menu for wordsTable
     */
    private void initWordsPopupMenu() {
    	wordsPopupMenu = new JPopupMenu();
    	wordsPopupEditWordItem = new JMenuItem("Edit Word");
    	wordsPopupShowPromptItem = new JMenuItem("Show Prompt");
    	wordsPopupShowPromptItem.setAccelerator(KS_SHOW_PROMPT);
    	wordsPopupShowAllPromptsItem = new JMenuItem("Show All Prompts");
    	wordsPopupShowAllPromptsItem.setAccelerator(KS_SHOW_ALL_PROMPTS);
    	wordsPopupCheckWordItem = new JMenuItem("Check Word");
    	wordsPopupCheckWordItem.setAccelerator(KS_CHECK_WORD);
    	wordsPopupCheckAllWordsItem = new JMenuItem("Check All Words");
    	wordsPopupCheckAllWordsItem.setAccelerator(KS_CHECK_ALL_WORDS);
		
    	wordsPopupMenu.add(wordsPopupEditWordItem);
    	wordsPopupMenu.addSeparator();
    	wordsPopupMenu.add(wordsPopupShowPromptItem);
    	wordsPopupMenu.add(wordsPopupShowAllPromptsItem);
    	wordsPopupMenu.addSeparator();
    	wordsPopupMenu.add(wordsPopupCheckWordItem);
    	wordsPopupMenu.add(wordsPopupCheckAllWordsItem);
    	
    	wordsPopupEditWordItem.addActionListener(new AbstractAction() {
					public void actionPerformed(ActionEvent e) {
						wordsPopupMenu.setVisible(false);
						editWord();
					}
				});

    	wordsPopupShowPromptItem.addActionListener(new AbstractAction() {
					public void actionPerformed(ActionEvent e) {
						wordsPopupMenu.setVisible(false);
						showCurrentPrompt();
					}
				});
    	
    	wordsPopupShowAllPromptsItem.addActionListener(new AbstractAction() {
					public void actionPerformed(ActionEvent e) {
						wordsPopupMenu.setVisible(false);
						showAllPrompts();
					}
				});
		
    	wordsPopupCheckWordItem.addActionListener(new AbstractAction() {
					public void actionPerformed(ActionEvent e) {
						wordsPopupMenu.setVisible(false);
						checkCurrentTestResult();
					}
				});
    	
    	wordsPopupCheckAllWordsItem.addActionListener(new AbstractAction() {
					public void actionPerformed(ActionEvent e) {
						wordsPopupMenu.setVisible(false);
						checkAllResults();
					}
				});
    }
   
    
    /**
     * Initializes wordsTable
     */
    private void initWordsTable()
    {
    	wordsTableModel = new TestTranslationTableModel(currentWords, testType);    
    	wordsTable = new JTable(wordsTableModel);
    	wordsTable.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);
    	wordsTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);

    	wordsTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    	
        TestTranslationSelectionListener wordsTableListener 
					= new TestTranslationSelectionListener(this);
        wordsTable.getSelectionModel().addListSelectionListener(wordsTableListener);
        wordsTable.getColumnModel().getSelectionModel()
							.addListSelectionListener(wordsTableListener);

        final JTextField textField = new JTextField();
    	LimitedPlainDocument document;
    	if(testType == TestManager.TRANSLATION) { 
			document = 
				new LimitedPlainDocument(Settings.TRANSLATION_MAX_LENGTH);
    	} else {
			document = 
				new LimitedPlainDocument(Settings.WORD_MAX_LENGTH);    		
    	}
    	textField.setDocument(document);
        TestTranslationCellEditor testEditor = 
        	new TestTranslationCellEditor(textField, currentWords);
        textField.addMouseListener(
				new MouseAdapter() {
					public void mouseClicked(MouseEvent ev) {
						wordsPopupMenu.setVisible(false);
						if(ev.getButton() == MouseEvent.BUTTON3) {
							Point textFieldLoc = 
									textField.getLocationOnScreen();
							Point tableFieldLoc = 
									wordsTable.getLocationOnScreen();
							int tableX = textFieldLoc.x + ev.getPoint().x 
									- tableFieldLoc.x;
							int tableY = textFieldLoc.y + ev.getPoint().y 
									- tableFieldLoc.y;
							Point tablePoint = new Point(tableX, tableY);
							int row = wordsTable.rowAtPoint(tablePoint);
							int column = wordsTable.columnAtPoint(tablePoint);
							if(!wordsTable.isRowSelected(row)) {
								wordsTable.clearSelection();
								wordsTable.setRowSelectionInterval(row, row);
								wordsTable.setColumnSelectionInterval(column, 
										column);
							}
							wordsPopupMenu.show(textField, ev.getPoint().x, 
									ev.getPoint().y);
						}
					}
				});

        wordsTable.setDefaultEditor(String.class, testEditor);
        
    	TestTranslationTableCellRenderer cellRenderer = 
    			new TestTranslationTableCellRenderer(currentWords);
    	wordsTable.setDefaultRenderer(String.class, cellRenderer);
    	wordsTable.setSelectionForeground(Color.BLACK);
    	
    	wordsTable.getTableHeader().setReorderingAllowed(false);
    	
    	if (testType == TestManager.TRANSLATION) {
    		wordsTable.getColumnModel().getColumn(0).setPreferredWidth(100);
        	wordsTable.getColumnModel().getColumn(1).setPreferredWidth(170);
        	wordsTable.getColumnModel().getColumn(2).setPreferredWidth(150);
        	wordsTable.getColumnModel().getColumn(3).setPreferredWidth(480);
    	} else {
    		wordsTable.getColumnModel().getColumn(0).setPreferredWidth(170);
        	wordsTable.getColumnModel().getColumn(1).setPreferredWidth(130);
        	wordsTable.getColumnModel().getColumn(2).setPreferredWidth(120);
        	wordsTable.getColumnModel().getColumn(3).setPreferredWidth(480);
    	}
    	 	
    	wordsTable.addMouseListener(
				new MouseAdapter() {
					public void mouseClicked(MouseEvent ev) {
						wordsPopupMenu.setVisible(false);
						if(ev.getButton() == MouseEvent.BUTTON3) {
							int row = wordsTable.rowAtPoint(ev.getPoint());
							wordsTable.requestFocus();
							if(!wordsTable.isRowSelected(row)) {
								wordsTable.clearSelection();
								wordsTable.setRowSelectionInterval(row, row);
							}
							wordsPopupMenu.show(wordsTable, ev.getPoint().x, ev.getPoint().y);
						}
					}
				});   	
    	
    	wordsScrollPane = new JScrollPane(wordsTable);    	
    }

    
    /**
	 * Gets from the heap next portion of words for test (randomly). And
	 * fills test table with them.
	 */
    private void nextAction() {
    	if (thereIsUncheckedWords()) {
            int n = ArrowOptionPane.showConfirmDialog(this,
            		"There are some unchecked words.\n" +
                    "Do you really want to move on?", "Warning", 
                    JOptionPane.YES_NO_OPTION);
            if(n != JOptionPane.OK_OPTION) {
            	return;
            }    		
    	}
    	
    	Utils.finishTableEditing(wordsTable);
    	
    	currentWords.removeAllElements();
    	int numberOfLines = (testType==TestManager.TRANSLATION)? 
    			Settings.getInt("translationNumberOfLines"):
    			Settings.getInt("reverseNumberOfLines");
		for(int i=0; i < numberOfLines; i++) {
			if(!hasNextWord())
				break;
			int pos = random.nextInt(heap.size());
			WordRecord currentWord = (WordRecord)heap.elementAt(pos);
			ThroughWordIndex currentIndex = (ThroughWordIndex)heapIndices.elementAt(pos);
            heap.remove(pos);
            heapIndices.remove(pos);
            
            TestTranslationRecord record = new TestTranslationRecord(currentWord);
            record.wordIndex = currentIndex;
            record.dottedPrompt = Utils.markWord(record.wordRecord.getPrompt(), 
            		record.wordRecord.getWord(), Utils.DOT_MARK);
            record.boldPrompt = "<html>" 
            		+ Utils.markWord(record.wordRecord.getPrompt(), 
            				record.wordRecord.getWord(), Utils.BOLD_MARK) 
            		+ "</html>";
            currentWords.addElement(record);
		}
		wordsTableModel.fireTableDataChanged();
		remainLabel.setText("Remain: " + heap.size());
		nextButton.setEnabled(hasNextWord());
		
		wordsTable.setRowSelectionInterval(0, 0);
		wordsTable.setColumnSelectionInterval(1, 1);
		wordsTable.requestFocus();
	}
	
    
    /**
     * Places UI components using different layout managers
     */
  	private void placeComponents() {
    	mainPanel.setLayout(new BorderLayout());
    	mainPanel.add(toolBar, BorderLayout.NORTH);
    	mainPanel.add(wordsScrollPane, BorderLayout.CENTER);
    	mainPanel.add(bottomPanel, BorderLayout.SOUTH);
    	  	   	   	
    	bottomPanel.setPreferredSize(new Dimension(200, 40));
    	
    	bottomPanel.setLayout(new BorderLayout());
    	bottomPanel.add(remainPanel, BorderLayout.WEST);
    	bottomPanel.add(buttonsPanel, BorderLayout.EAST);
    	
    	remainPanel.setPreferredSize(new Dimension(100, 40));
    	
    	remainPanel.setLayout(null);
    	remainPanel.add(remainLabel);
    	
    	remainLabel.setBounds(10, 10, 70, 20);
    	
    	buttonsPanel.setPreferredSize(new Dimension(210, 40));
    	buttonsPanel.setLayout(null);
    	buttonsPanel.add(nextButton);
    	buttonsPanel.add(terminateButton);
    	
    	nextButton.setBounds(10, 10, 90, 20);
    	terminateButton.setBounds(110, 10, 90, 20);    	
  	}
    
    
    /**
  	 * Enable/disable toolbars buttons depending on selected words.
  	 */
    private void refreshWordsButtons() {
    	int index = wordsTable.getSelectedRow();
    	boolean enableButton = (index >= 0) && (index < currentWords.size()); 
    	editWordButton.setEnabled(enableButton);
    	showPromptButton.setEnabled(enableButton);
    	checkWordButton.setEnabled(enableButton);
    	
    	wordsPopupEditWordItem.setEnabled(enableButton);
    	wordsPopupShowPromptItem.setEnabled(enableButton);
    	wordsPopupCheckWordItem.setEnabled(enableButton);
    }
       
    
    /**
     * Terminate button action handler. Finishes test.
     */
    private void terminateAction() {
    	if (thereIsUncheckedWords()) {
            int n = ArrowOptionPane.showConfirmDialog(this,
            		"There are some unchecked words.\n" +
                    "Do you really want to end test?", "Warning", 
                    JOptionPane.YES_NO_OPTION);
            if(n != JOptionPane.OK_OPTION) {
            	return;
            }    		
    	}
    	
    	setVisible(false);
    }

    
    /**
     * Checkes if there are any unchecked words in currently shown 
     * words list 
     * @return true, if there are any unchecked words
     */
    private boolean thereIsUncheckedWords() {
        for(int i=0; i<currentWords.size(); i++) {
            TestTranslationRecord record = currentWords.elementAt(i);
            if(record.checkResult == TestTranslationRecord.UNKNOWN) {
            	return true;
            }
        }
        return false;
    }
    
    
    /**
     * TestTranslationCellEditor class represents cell editor for words table 
     * in TestTranslationDialog
     * @author Konstantin Fadeyev
     */
    private class TestTranslationCellEditor extends AbstractCellEditor 
			implements TableCellEditor {
    	
    	/**
    	 * Vector of TestTranslationRecord containing currently shown words 
    	 * and related information
    	 */
        private Vector<TestTranslationRecord> currentWords; 
        private JTextField textField;

        
        public TestTranslationCellEditor(JTextField _textField, 
        		Vector<TestTranslationRecord> currentWords) {
        	textField = 
        		TableTextFieldFactory.createTableTextField(_textField);
        	this.currentWords = currentWords;
        }
      
        public Object getCellEditorValue() {
        	return textField.getText();
        }
        
        public Component getTableCellEditorComponent(JTable table,Object value,
        						boolean isSelected, int row, int column) {
        	Component editor = textField;
        	textField.setText((String)value);
            if(column!=1 || row >= currentWords.size()) {
            	editor.setBackground(Color.white);
            } else {
            	TestTranslationRecord record = 
    				(TestTranslationRecord)currentWords.elementAt(row);
                switch(record.checkResult) {
                case TestTranslationRecord.UNKNOWN:
                	editor.setBackground(Color.white);
                    break;
                case TestTranslationRecord.RIGHT:
                	editor.setBackground(RIGHT_COLOR);
                    break;
                case TestTranslationRecord.WRONG:
                	editor.setBackground(WRONG_COLOR);
                }
            }    	
            return editor;
        }
    }
    
    
    /**
     * TestTranslationRecord class is used to keep information about shown word
     * (word text, user answer, is word checked or not and etc.)
     * @author Konstantin Fadeyev
     *
     */
    private class TestTranslationRecord {
    	public static final int UNKNOWN = 0;
    	
    	public static final int RIGHT = 1;
    	
    	public static final int WRONG = 2;
    	public WordRecord wordRecord;
    	/**
    	 * Index of word to keep track of word's place in dictionary
    	 */
    	public ThroughWordIndex wordIndex;
    	public String userAnswer;   
    	public boolean showPrompt;
    	public boolean showAnswer;

    	
    	public int checkResult;
    	public String dottedPrompt;
    	public String boldPrompt;
    	
    	public TestTranslationRecord(WordRecord w) {
    		wordRecord = w;
    		showPrompt = false;
    		showAnswer = false;
    		checkResult = UNKNOWN;
    		userAnswer = "";
    	}
    }
    
    
    /**
     * TestTranslationSelectionListener class represents selection listener 
     * for words table in TestTranslationDialog
     * @author Konstantin Fadeyev
     *
     */
    private class TestTranslationSelectionListener 
    		implements ListSelectionListener {
    	TestTranslationDialog dialog;
    	
    	public TestTranslationSelectionListener(TestTranslationDialog d) {
    	    dialog = d;
    	}
    	
    	public void valueChanged(ListSelectionEvent e) {
    		dialog.refreshWordsButtons();
    	}
    }

    
    /**
     * TestTranslationTableCellRenderer class represents cell renderer 
     * for words table in TestTranslationDialog
     * @author Konstantin Fadeyev
     *
     */
    private class TestTranslationTableCellRenderer extends DefaultTableCellRenderer {
    	
    	/**
    	 * Vector of TestTranslationRecord containing currently shown words 
    	 * and related information
    	 */
        private Vector<TestTranslationRecord> currentWords; 	
    	
        public TestTranslationTableCellRenderer(Vector<TestTranslationRecord> currentWords) {
        	this.currentWords = currentWords;
        }

        public Component getTableCellRendererComponent(JTable table, 
        		Object value, boolean isSelected, boolean hasFocus, 
        		int row, int column) {
            Component cell = super.getTableCellRendererComponent(table, 
            		value, isSelected, hasFocus, row, column);
            cell.setForeground(Color.black);
            if(column!=1 || row >= currentWords.size()) {
                cell.setBackground(Color.white);
                if(isSelected)
                    cell.setBackground(SELECTION_COLOR);
                if(hasFocus)
                    cell.setBackground(Color.white);
            } else {
            	TestTranslationRecord record = 
    				(TestTranslationRecord)currentWords.elementAt(row);
                switch(record.checkResult) {
                case TestTranslationRecord.UNKNOWN:
                    cell.setBackground(Color.white);
                    if(isSelected)
                        cell.setBackground(SELECTION_COLOR);
                    if(hasFocus)
                        cell.setBackground(Color.white);
                    break;
                case TestTranslationRecord.RIGHT:
                    cell.setBackground(RIGHT_COLOR);
                    if(isSelected)
                        cell.setBackground(SELECTION_RIGHT_COLOR);
                    if(hasFocus)
                        cell.setBackground(RIGHT_COLOR);
                    break;
                case TestTranslationRecord.WRONG:
                    cell.setBackground(WRONG_COLOR);
                    if(isSelected)
                        cell.setBackground(SELECTION_WRONG_COLOR);
                    if(hasFocus)
                        cell.setBackground(WRONG_COLOR);
                }
            }
            if(hasFocus && isSelected) {
                TableModel tblModel = table.getModel();
                if(tblModel.isCellEditable(row, column)) {
                    table.editCellAt(row, column);
                    table.getEditorComponent().requestFocus();
                }
            }
            return cell;
        }
    }
    
    
    /**
     * TestTranslationTableModel class represents table model
     * for words table in TestTranslationDialog
     * @author Konstantin Fadeyev
     *
     */
    private class TestTranslationTableModel extends AbstractTableModel {
    	
    	/**
    	 * Vector of TestTranslationRecord containing currently shown words 
    	 * and related information
    	 */
        private Vector<TestTranslationRecord> currentWords; 
        
        private boolean hideOriginalWord;
        private int tableSize;
            	
        
    	public TestTranslationTableModel(Vector<TestTranslationRecord> currentWords, 
    			int testType)	{
    		super();
    		this.currentWords = currentWords;
    		if(testType == TestManager.TRANSLATION) {
    			hideOriginalWord = false;
    		} else {
    			hideOriginalWord = true;
    		}
    		tableSize =  (testType==TestManager.TRANSLATION)? 
        			Settings.getInt("translationNumberOfLines"):
            			Settings.getInt("reverseNumberOfLines");
    	}

    	public Class<?> getColumnClass(int c) {
            return getValueAt(0, c).getClass();
        }

    	public int getColumnCount() {
    		return 4;
    	}

    	public String getColumnName(int num) {
    		switch(num) {
    		case 0:
    			return "Word";
    		case 1:
    			return "Your translate";
    		case 2:
    			return "Right translate";
            default:
                return "Prompt";
    		}
    	}

        public int getRowCount() {
    	    return tableSize;
    	}

    	public Object getValueAt(int row, int column) {
    		switch(column) {
    		case 0:
    			if(row<currentWords.size()) {
    				TestTranslationRecord record = 
    					(TestTranslationRecord)currentWords.elementAt(row);
    		        String wordVersion = (testType == TestManager.TRANSLATION)
            		? record.wordRecord.getWord() 
            		: record.wordRecord.getTranslation();
    				return wordVersion;
    			} else {
    				return "";
    			}
    		case 1:
    			if(row<currentWords.size()) {
    				TestTranslationRecord record = 
    					(TestTranslationRecord)currentWords.elementAt(row);
    				return record.userAnswer;
    			} else {
    				return "";
    			}
    		case 2:
    			if(row < currentWords.size()) {
    				TestTranslationRecord record = 
    					(TestTranslationRecord)currentWords.elementAt(row);
                    if(record.showAnswer) {  
                    	String wordVersion = (testType == TestManager.TRANSLATION)
                		? record.wordRecord.getTranslation() 
                		: record.wordRecord.getWord();
                    	return wordVersion;
                    } else {
                        return "";
                    }
       			} else {
    				return "";
    			}
            default:
    			if(row<currentWords.size()) {
    				TestTranslationRecord record = 
    					(TestTranslationRecord)currentWords.elementAt(row);
                    if(record.showPrompt)
                    	if(hideOriginalWord && !record.showAnswer) {
                    		return record.dottedPrompt;
                    	} else {
                    		return record.boldPrompt;
                    	}
                    else
                        return "";
    			} else {
    				return "";
    			}
    		}
    	}
    	
    	public boolean isCellEditable(int rowIndex, int columnIndex) {
    		switch(columnIndex) {
    		case 0:
    			return false;
    		case 1:
    			if(rowIndex<currentWords.size())
    				return true;
    			else
    				return false;
    		default:
    			return false;
    		}
        }

        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
    		if(columnIndex!=1)
    			return;
    		
            if(rowIndex>=currentWords.size())
    	        return;
            
            TestTranslationRecord record = 
    			(TestTranslationRecord)currentWords.elementAt(rowIndex);
            record.userAnswer = (String)aValue;
    	}
    }

}
